//package crypto;
import java.io.*;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.*;

public class CryptAnalyzer {

	private static final String currentDirectory = System.getProperty("user.dir") + "/";
	private static String cipherTextDirectory;
    private Vector<Character[][]> potentialPads;
    private Vector<Character[][]> pads;
    private Vector<String> cipherTextList;
    private int messageCount;
    private int numberOfRows = 50;
    private int numberOfColumns = 80;

    public CryptAnalyzer(String cipherDirectory) {
		cipherTextDirectory = currentDirectory + cipherDirectory;
		File dir = new File(cipherTextDirectory);
		String[] cipherFilenameList = dir.list();

        potentialPads = new Vector<Character[][]>();
        pads = new Vector<Character[][]>();
        cipherTextList = new Vector<String>();
//        messageCount = 1 + cipherFilenameList.length() - cipherFilenameList.replaceAll("\\,", "").length();
		messageCount = cipherFilenameList.length;
        for (String cipherTextFilename : cipherFilenameList) {
            try {
                StringBuffer fileData = new StringBuffer(4000);
                BufferedReader reader = new BufferedReader(
                    new FileReader(cipherTextDirectory + "/" + cipherTextFilename)
                );
                char[] buf = new char[4000];
                String readerContents = reader.readLine();
             //   assert(readerContents.length() == 4000);
                buf = readerContents.toCharArray();
                
                // i don't need buf. i'll just use readerContents
                
                cipherTextList.add(readerContents);
                //messageCount++;
                
                reader.close();
                System.out.println(fileData.toString());
            } catch (Exception e) {
                System.err.println("ERROR: " + e);
            }
        }
    }

    public void retrievePads() {
    	try {
    		String[] pads;
    		String decryptedText;
    		boolean isFound = false;
    		boolean notMatch = true;
    		for (int i = 1; i <= 16; i++) {
//    		for (int i = 16; i >= 1; i--) {
    			pads = getPotentialPads(i);
    			for (int j = 1; j <= i; j++) {
    				decryptedText = decryptString(cipherTextList.elementAt(j-1), pads[j-1]);
    				if (isPrintableASCIIString(decryptedText) < 0.5) {
    					notMatch = false;
    					break;
    				} else if (j == i && isPrintableASCIIString(decryptedText) > 0.7 && isValidPlainText(decryptedText)) {
    					isFound = true;
    				}
    			}
    			if (isFound == true || i == 16) {
    				// output the pads
    				String basePadName = "pad";
    				for(int padNum = 0; padNum < i; padNum++) {
    					System.out.println("pad" + padNum);
    					System.out.println(pads[padNum]);
    					// here
    					pads[padNum] = pads[padNum].replaceAll("null", "");
    					outputPad(("padFilesRecovered/" + (padNum+1)) + ".txt", pads[padNum]);
    				}
    				break;
    			}
    			if (notMatch == false) {
    				continue;
    			}
    		}
    	} catch (Exception e) {
    		System.err.println("ERROR in retrievePads: " + e.getMessage());
    	}
    }

    /*
    Algorithm:
    * for each character position, 1-4000, find the most commonly occurring ciphertext
    character.
    * assume that the plaintext for that character is 'e'
    * run getSinglePad() for each character position, using the most common ciphertext
    character and 'e'
    */
    public String[] getPotentialPads(int assumedPadCount) {
//        System.out.println("Message Count: " + messageCount + "\n");
        String[] potentialPads = new String[assumedPadCount];
        String potentialPad = "";
        //int[] charCount = new int[127];
        int currentChar;

        for (int i = 1; i <= 4000; i++) {
//        	int[] charCount = new int[127];
        	/* -- uncomment later
            if (i % 80 == 0) {
                // the last column of pad is a space
                potentialPad += " ";
                continue;
            }
        	 */
        	for (int x = 1; x <= assumedPadCount; x++) {
        		int[] charCount = new int[127];
        		for (int j = x; j <= messageCount; j += assumedPadCount) {
        			// if it's not the last column of the 50x80 matrix
        			currentChar = (int) cipherTextList.elementAt(j-1).charAt(i-1);
        			charCount[currentChar] += 1;
        		}

        		// call getSinglePad, then add the result to potentialPad
        		char mostCommonChar = getMostCommonChar(charCount);
        		potentialPads[x-1] += getSinglePad(mostCommonChar, ' ');
        	}
        }
        assert(potentialPads[0].length() == 4000);

        return potentialPads;
    }

    private void outputPad(String outputFilename, String pad) {
        String currentDirectory = System.getProperty("user.dir") + "/";
        try {
            FileWriter fw = new FileWriter(currentDirectory + "/" + outputFilename);
            fw.write(pad);
            fw.close();
        } catch (Exception e) {
            System.err.println("ERROR: " + e);
        }
    }

    public String getSinglePad(char cipherChar, char plainText) {
        char pad = 0;
        // guess 1
        pad = (char) ((int) cipherChar - 32 + 64 - (int) plainText);
        if (isPrintableASCII(pad)) {
            return Character.toString(pad);
        }
        // guess 2
        pad = (char) (cipherChar - 32 + 95 + 64 - plainText);
        return Character.toString(pad);
    }

    // helper methods
    private boolean isPrintableASCII(char c) {
        if (((int) c) >= 32 && ((int) c) <= 126) {
            return true;
        }
        return false;
    }
    
    private float isPrintableASCIIString(String s) {
    	float printablePercent;
    	int printableCount = 0;
    	for (int i = 0; i < s.length(); i++) {
    		if (isPrintableASCII(s.charAt(i)) == true) {
    			printableCount++;
    		}
    	}
    	printablePercent = printableCount / s.length();
    	return printablePercent;
    }

    private char getMostCommonChar(int[] charCount) {
        int max = 0;
        int maxIndex = 0;

        for (int i = 0; i < charCount.length; i++) {
            if (charCount[i] > max) {
                max = charCount[i];
                maxIndex = i;
            }
        }

        return (char) maxIndex;
    }
    
    private String decryptString(String cipherText, String pad) {
    	String decryptedText = "";
    	int currentChar;
    	pad = pad.replaceAll("null", "");

    	for (int i = 1; i <= 4000; i++) {
    		char cipherChar = cipherText.charAt(i-1);
    		char padChar = pad.charAt(i-1);
    		currentChar = (((int)cipherChar-32)-((int)padChar-32));
    		if (currentChar < 0) {
    			currentChar += 95;
    		}
    		currentChar = currentChar % 95;
//    		currentChar = (char) (((cipherChar-32)-(pad.charAt(i-1)-32))%95);
    		currentChar = (currentChar + 32);
    		decryptedText += Character.toString((char)currentChar);
    	}
    	return decryptedText;
    }
    
    private boolean isValidPlainText(String suspect) {
    	float averageWordLength = getAverageWordLength(suspect);
    	if (averageWordLength > 7.0) {
    		return false;
    	}
    	// most common character is ' ' or e
    	boolean isValid = true;
    	int[] charCount = new int[127];
    	for (int i = 1; i <= suspect.length(); i++) {
    		charCount[suspect.charAt(i-1)]++;
    	}
    	char mostCommonChar = getMostCommonChar(charCount);
    	if (mostCommonChar != ' ') {
    		return false;
    	}
    	charCount[' '] = 0;
    	mostCommonChar = getMostCommonChar(charCount); // get the second most common character
    	if (mostCommonChar != 'e') {
    		return false;
    	}
    	charCount['e'] = 0;
    	mostCommonChar = getMostCommonChar(charCount); // get the third most common character
    	if (!Character.isLowerCase(mostCommonChar)) {
    		return false;
    	}
    	charCount[mostCommonChar] = 0;
    	mostCommonChar = getMostCommonChar(charCount); // get the 4th most common character
    	if (!Character.isLowerCase(mostCommonChar)) {
    		return false;
    	}
//    	if (mostCommonChar == 'e' || mostCommonChar == ' ') {
//    		return true;
//    	}
    	// contains 'the' or 'of'
    	if (suspect.contains(" the ") || suspect.contains(" of ")) {
    		return true;
    	}
    	return false;
    }
    
    private float getAverageWordLength(String input) {
    	int wordCount = 1 + input.length() - input.replaceAll(" ", "").length();
    	return (float) 4000 / wordCount;
    }
}
